// 定义坐标类型
export interface Point {
  lng: number
  lat: number
}

/**
 * 坐标转换工具
 * 提供 WGS-84 和 GCJ-02 (火星坐标系) 之间的坐标转换功能
 */

/**
 * 将 WGS-84 坐标转换为 GCJ-02 (火星坐标系) 坐标
 * @param {number} lng - 经度
 * @param {number} lat - 纬度
 * @returns {Array<number>} - 转换后的 [经度, 纬度] 数组
 */
export const wgs84ToGcj02 = (wgsPoint: Point): Point => {
  const { lng, lat } = wgsPoint
  // 先将坐标反向转换，然后使用反向转换的结果计算 GCJ-02 坐标
  let reverse = gcj02ToWgs84({ lng, lat })
  return {
    lng: lng * 2 - reverse.lng,
    lat: lat * 2 - reverse.lat,
  }
}

/**
 * 将 GCJ-02 (火星坐标系) 坐标转换为 WGS-84 坐标
 * @param {number} lng - 经度
 * @param {number} lat - 纬度
 * @returns {Array<number>} - 转换后的 [经度, 纬度] 数组
 */
export const gcj02ToWgs84 = (gcjPoint: Point): Point => {
  const { lng, lat } = gcjPoint
  // 地球椭球参数
  let epsilon = 0.006693421622965823 // 第一偏心率平方
  let radLat = (lat / 180) * Math.PI // 纬度转弧度
  let radius = 6378245 // 地球赤道半径
  let magic = Math.sin(radLat)
  magic = 1 - epsilon * magic * magic
  magic = Math.sqrt(magic)

  // 计算偏移量
  let x = lng - 105
  let y = lat - 35
  let a =
    ((20 * Math.sin(6 * x * Math.PI) + 20 * Math.sin(2 * x * Math.PI)) * 2) / 3
  let dLng =
    300 + x + 2 * y + 0.1 * x * x + 0.1 * x * y + 0.1 * Math.sqrt(Math.abs(x))
  dLng += a
  dLng +=
    ((20 * Math.sin(x * Math.PI) + 40 * Math.sin((x / 3) * Math.PI)) * 2) / 3
  dLng +=
    ((150 * Math.sin((x / 12) * Math.PI) + 300 * Math.sin((x / 30) * Math.PI)) *
      2) /
    3
  let dLat =
    -100 +
    2 * x +
    3 * y +
    0.2 * y * y +
    0.1 * x * y +
    0.2 * Math.sqrt(Math.abs(x))
  dLat += a
  dLat +=
    ((20 * Math.sin(y * Math.PI) + 40 * Math.sin((y / 3) * Math.PI)) * 2) / 3
  dLat +=
    ((160 * Math.sin((y / 12) * Math.PI) + 320 * Math.sin((y / 30) * Math.PI)) *
      2) /
    3

  // 转换为度数
  dLng = (dLng * 180) / ((radius / magic) * Math.cos(radLat) * Math.PI)
  dLat = (dLat * 180) / (((radius * (1 - epsilon)) / (magic * magic)) * Math.PI)
  return {
    lng: lng - dLng,
    lat: lat - dLat,
  }
}

/**
 * 批量转换 BD-09 坐标数组到 GCJ-02
 * @param bdPoints 百度坐标点数组
 * @returns GCJ-02 坐标点数组
 */
export const batchBd09ToGcj02 = (bdPoints: Point[]): Point[] => {
  return bdPoints.map(point => bd09ToGcj02(point))
}

/**
 * 将百度坐标系 (BD-09) 转换为火星坐标系 (GCJ-02)
 * @param bdPoint 百度坐标点 { lng: number, lat: number }
 * @returns GCJ-02 坐标点 { lng: number, lat: number }
 */
export const bd09ToGcj02 = (bdPoint: Point): Point => {
  // 常量定义
  const X_PI: number = (Math.PI * 3000.0) / 180.0

  // 解构坐标值
  const { lng: bdLon, lat: bdLat } = bdPoint

  // 坐标转换算法
  const x: number = bdLon - 0.0065
  const y: number = bdLat - 0.006

  const z: number = Math.sqrt(x * x + y * y) - 0.00002 * Math.sin(y * X_PI)
  const theta: number = Math.atan2(y, x) - 0.000003 * Math.cos(x * X_PI)

  // 计算结果
  const gcjLon: number = z * Math.cos(theta)
  const gcjLat: number = z * Math.sin(theta)

  return { lng: gcjLon, lat: gcjLat }
}

/**
 * 批量转换 GCJ-02 坐标数组到 BD-09
 * @param gcjPoints 火星坐标点数组
 * @returns 百度坐标点数组
 */
export const batchGcj02ToBd09 = (gcjPoints: Point[]): Point[] => {
  return gcjPoints.map(point => gcj02ToBd09(point))
}
/**
 * GCJ-02 转 BD-09 坐标系
 * @param gcjPoint 火星坐标点 { lng: number, lat: number }
 * @returns 百度坐标点 { lng: number, lat: number }
 */

const X_PI: number = (Math.PI * 3000.0) / 180.0
export const gcj02ToBd09 = (gcjPoint: Point): Point => {
  // 解构坐标值
  const { lng: gcjLon, lat: gcjLat } = gcjPoint

  // 坐标转换算法
  const x: number = gcjLon
  const y: number = gcjLat

  const z: number = Math.sqrt(x * x + y * y) + 0.00002 * Math.sin(y * X_PI)
  const theta: number = Math.atan2(y, x) + 0.000003 * Math.cos(x * X_PI)

  // 计算结果并添加固定偏移
  const bdLon: number = z * Math.cos(theta) + 0.0065
  const bdLat: number = z * Math.sin(theta) + 0.006

  return { lng: bdLon, lat: bdLat }
}

export const coordinateConverter = {
  wgs84ToGcj02,
  gcj02ToWgs84,
  gcj02ToBd09,
  batchGcj02ToBd09,
  bd09ToGcj02,
  batchBd09ToGcj02,
}
